use std::collections::HashMap;

use derive_builder::Builder;
use serde::{Deserialize, Serialize};

use crate::{
    database::DatabaseType,
    error::Error,
    modeling::{
        ClassAttribute, ClassAttributeBuilder, DataSourceClassAttributeMapping, Value, ValueType,
    },
};

#[derive(Debug, Serialize, Deserialize, Default, Clone, Builder, PartialEq)]
#[builder(name = "ClassAttributePersistenceBuilder")]
#[builder(pattern = "mutable")]
#[builder(setter(into, strip_option), default)]
#[builder(derive(Debug))]
#[builder(build_fn(error = "Error"))]
pub struct ClassAttributePersistence {
    //
    pub g01_attribute: String,

    pub column_name: String,

    pub column_default: String,

    pub ordinal_position: u32,

    pub is_nullable: bool,

    pub data_type: String,

    pub column_type: String,

    pub column_key: String,

    pub description: String,
}

impl TryFrom<Value> for ClassAttributePersistence {
    type Error = anyhow::Error;

    fn try_from(value: Value) -> Result<Self, Self::Error> {
        let v: serde_json::Value = value.try_into()?;
        Ok(serde_json::from_value(v).map_err(|err| anyhow::anyhow!("{}", err))?)
    }
}

impl TryInto<Value> for ClassAttributePersistence {
    type Error = anyhow::Error;

    fn try_into(self) -> Result<Value, Self::Error> {
        Ok(serde_json::to_value(self)
            .map_err(|err| anyhow::anyhow!("{}", err))?
            .try_into()?)
    }
}

impl TryFrom<ClassAttribute> for ClassAttributePersistence {
    type Error = anyhow::Error;

    fn try_from(attr: ClassAttribute) -> Result<Self, Self::Error> {
        ClassAttributePersistence::try_from(&attr)
    }
}

impl TryInto<ClassAttribute> for ClassAttributePersistence {
    type Error = anyhow::Error;

    fn try_into(self) -> Result<ClassAttribute, Self::Error> {
        let mut builder = ClassAttributeBuilder::default();
        builder
            .identifier(self.g01_attribute)
            .name(&self.column_name)
            .title(&self.column_name);

        Ok(builder.build()?)
    }
}

impl TryFrom<&ClassAttribute> for ClassAttributePersistence {
    type Error = anyhow::Error;

    fn try_from(attr: &ClassAttribute) -> Result<Self, Self::Error> {
        let mut builder = ClassAttributePersistenceBuilder::default();

        builder
            .g01_attribute(&attr.identifier)
            .column_name(&attr.name)
            .column_default(
                &attr
                    .default_value
                    .as_ref()
                    .map_or(String::new(), |v| v.clone()),
            )
            .ordinal_position(attr.options.as_ref().map_or(0_u32, |v| {
                if let Ok(s) = v.parse() {
                    s
                } else {
                    0_u32
                }
            }))
            .is_nullable(attr.is_nullable);

        Ok(builder.build()?)
    }
}

impl TryFrom<DataSourceClassAttributeMapping> for ClassAttributePersistence {
    type Error = anyhow::Error;

    fn try_from(attr: DataSourceClassAttributeMapping) -> Result<Self, Self::Error> {
        ClassAttributePersistence::try_from(&attr)
    }
}

impl TryFrom<&DataSourceClassAttributeMapping> for ClassAttributePersistence {
    type Error = anyhow::Error;

    fn try_from(attr: &DataSourceClassAttributeMapping) -> Result<Self, Self::Error> {
        let mut builder = ClassAttributePersistenceBuilder::default();

        builder
            .g01_attribute(&attr.class_attribute_identifier)
            .column_name(&attr.column_name);

        Ok(builder.build()?)
    }
}

#[async_trait::async_trait]
pub trait AttributeValueConvertor {
    async fn get_attribute_value(
        &self,
        class_identifier: &String,
        name: &String,
        value: &Value,
    ) -> anyhow::Result<Value>;

    async fn get_attribute_values(
        &self,
        class_identifier: &String,
        name: &String,
        values: &Vec<Value>,
    ) -> anyhow::Result<Vec<Value>>;

    async fn get_persistence_value(
        &self,
        class_identifier: &String,
        name: &String,
        value: &Value,
        database_type: Option<DatabaseType>,
    ) -> anyhow::Result<Value>;

    async fn get_persistence_values(
        &self,
        class_identifier: &String,
        name: &String,
        values: &Vec<Value>,
        database_type: Option<DatabaseType>,
    ) -> anyhow::Result<Vec<Value>>;
}
